﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#include "GLWindow.h"
#include "Platform.h"

#ifdef PLATFORM_WINDOWS_SHELL
#ifdef UX_OPENGL_WINDOW_HANDLES

namespace UX_STL
{
namespace _platform_
{
	
//-------------------------- GLWindow ----------------------------//
	
	static	PFNWGLSWAPINTERVALEXTPROC			wglSwapIntervalEXT			= null;
	static	PFNWGLGETSWAPINTERVALEXTPROC		wglGetSwapIntervalEXT		= null;
	static	PFNWGLCHOOSEPIXELFORMATARBPROC		wglChoosePixelFormatARB		= null;
	static	PFNWGLCREATECONTEXTATTRIBSARBPROC	wglCreateContextAttribsARB	= null;
	static	bool										C_WGL_EXT_swap_control		= false;
	
# ifdef _UXS_STL_CONFIGURATION_H
	TGLContext const *	_s_pCurrentGLContext	= null;
# endif

	
/*
=================================================
	
=================================================
*/
	GLWindow::GLWindow()
	{
	}
	
/*
=================================================
	
=================================================
*/
	GLWindow::~GLWindow()
	{
	}

/*
=================================================
	
=================================================
*/
	bool GLWindow::s_InitGL()
	{
		static bool	s_bIsInited = false;

		if ( s_bIsInited )
			return true;

		CWindow			s_wnd;
		TDescriptor		s_desc;
		THandles &		s_hnd	= s_wnd.HandlesRef();
		bool			ret		= false;
		
		s_desc.bRawInput = false;
		
		s_wnd.Create( s_desc );

		s_hnd.hDC = GetDC( s_hnd.hWnd );
		
		PIXELFORMATDESCRIPTOR pfd = { sizeof( PIXELFORMATDESCRIPTOR ), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA,
									  32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 };

		const uint	u_pixformat = ChoosePixelFormat( s_hnd.hDC, &pfd );

		if ( u_pixformat != 0 )
		{
			SetPixelFormat( s_hnd.hDC, u_pixformat, &pfd );
			s_hnd.hRC = wglCreateContext( s_hnd.hDC );

			if ( s_hnd.hRC != null and wglMakeCurrent( s_hnd.hDC, s_hnd.hRC ) == TRUE )
			{
				wglChoosePixelFormatARB		= (PFNWGLCHOOSEPIXELFORMATARBPROC) wglGetProcAddress("wglChoosePixelFormatARB");
				wglCreateContextAttribsARB	= (PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
		
				wglSwapIntervalEXT			= (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT");
				wglGetSwapIntervalEXT		= (PFNWGLGETSWAPINTERVALEXTPROC) wglGetProcAddress("wglGetSwapIntervalEXT");

				C_WGL_EXT_swap_control		= (wglSwapIntervalEXT != null) and (wglGetSwapIntervalEXT != null);

				ret = true;
			}
			wglMakeCurrent( s_hnd.hDC, null );

			wglDeleteContext( s_hnd.hRC );
			s_hnd.hRC = null;
		}
		
		ReleaseDC( s_hnd.hWnd, s_hnd.hDC );
		s_hnd.hDC = null;

		s_bIsInited = true;
		return ret;
	}

/*
=================================================
	
=================================================
*/
	bool GLWindow::Create (const TDescriptor &sDesc, uni_c_string pCaption, const TWndClass &sWndClass)
	{
		return CreateGL( TDescriptorExt(), pCaption, sWndClass );
	}
	
/*
=================================================
	
=================================================
*/
	bool GLWindow::CreateGL (const TDescriptorExt &sDesc, uni_c_string pCaption, const TWndClass &sWndClass)
	{
		_s_RegisterClass( _sHandles, sWndClass );
		return _CreateGL( sDesc, pCaption );
	}
	
	bool GLWindow::_CreateGL (const TDescriptorExt &sDesc, uni_c_string pCaption)
	{
		CHECK_ERR( CWindow::_Create( sDesc.sDesc, pCaption ) );

		CHECK_ERR( GLWindow::s_InitGL() );	// init wgl extended functions

		_sGLDescriptor = TGLDescriptor();

		_sHandles.hDC = GetDC( _sHandles.hWnd );
		
		bool	b_doublebuf = EnumCmp( sDesc.sGLDesc.eFlags, e_glinit_flags::DOUBLE_BUFFER );
		bool	b_stereo	= EnumCmp( sDesc.sGLDesc.eFlags, e_glinit_flags::STEREO );

		PIXELFORMATDESCRIPTOR pfd =
		{
			sizeof( PIXELFORMATDESCRIPTOR ),		// Size Of This Pixel Format Descriptor
			1,										// Version Number
			PFD_DRAW_TO_WINDOW |					// Format Must Support Window
			PFD_SUPPORT_OPENGL |					// Format Must Support OpenGL
			(b_doublebuf ? PFD_DOUBLEBUFFER : 0) |	// Must Support Double Buffering
			(b_stereo ? PFD_STEREO : 0),			// Support Stereo
			PFD_TYPE_RGBA,							// Request An RGBA Format
			sDesc.sGLDesc.uColorBits,				// Select Our Color Depth
			0, 0, 0, 0, 0, 0,						// Color Bits Ignored
			0,										// No Alpha Buffer
			0,										// Shift Bit Ignored
			0,										// No Accumulation Buffer
			0, 0, 0, 0,								// Accumulation Bits Ignored
			sDesc.sGLDesc.uDepthBits,				// Z-Buffer (Depth Buffer)  
			sDesc.sGLDesc.uStencilBits,				// Stencil Buffer
			0,										// No Auxiliary Buffer
			PFD_MAIN_PLANE,							// Main Drawing Layer
			0,										// Reserved
			0, 0, 0									// Layer Masks Ignored
		};

		uint			u_pixfmt = 0;
		HGLRC	h_rc	 = null;


		// choose pixel format
		for (int i = 0; i < 6; ++i)
		{
			switch ( i )
			{
				case 1 :
					i = 1;
					if ( b_stereo ) {
						pfd.dwFlags &= ~PFD_STEREO;
						b_stereo	= false;
						LOG( "Disable stereo.", e_log_msg::WARNING_3 );
						break;
					}

				case 2 :
					i = 2;
					if ( pfd.cStencilBits != 0 ) {
						pfd.cStencilBits = 0;
						break;
					}

				case 3 :
					i = 3;
					if ( pfd.cDepthBits != 24 ) {
						pfd.cDepthBits = 24;
						break;
					}

				case 4 :
					i = 4;
					if ( pfd.cDepthBits != 16 ) {
						pfd.cDepthBits = 16;
						break;
					}

				case 5 :
					i = 5;
					if ( pfd.dwFlags & PFD_DOUBLEBUFFER ) {
						pfd.dwFlags &= ~PFD_DOUBLEBUFFER;
						LOG( "Disable double buffer.", e_log_msg::WARNING_3 );
						break;
					}

				default :
					break;
			}
			
			if ( (u_pixfmt = ChoosePixelFormat( _sHandles.hDC, &pfd )) )
			{
				_sGLDescriptor.uDepthBits	= pfd.cDepthBits;
				_sGLDescriptor.uStencilBits	= pfd.cStencilBits;
				break;
			}
		}

		CHECK_ERR( u_pixfmt != 0 );

		
		// choose pixel format with multisample anti-aliasing
		if ( sDesc.sGLDesc.uMSAASamples > 1 and wglChoosePixelFormatARB != null )
		{
			float	fa_attributes[] = {0.0, 0.0};

			int		a_attributes[] =
			{
				WGL_DRAW_TO_WINDOW_ARB,		GL_TRUE,
				WGL_SUPPORT_OPENGL_ARB,		GL_TRUE,
				WGL_ACCELERATION_ARB,		WGL_FULL_ACCELERATION_ARB,
				WGL_COLOR_BITS_ARB,			sDesc.sGLDesc.uColorBits,
				WGL_ALPHA_BITS_ARB,			(sDesc.sGLDesc.uColorBits == 32) ? 8 : 1,
				WGL_DEPTH_BITS_ARB,			sDesc.sGLDesc.uDepthBits,
				WGL_STENCIL_BITS_ARB,		sDesc.sGLDesc.uStencilBits,
				WGL_DOUBLE_BUFFER_ARB,		b_doublebuf ? GL_TRUE : GL_FALSE,
				WGL_PIXEL_TYPE_ARB,			WGL_TYPE_RGBA_ARB,

				// FSAA //
				WGL_SAMPLE_BUFFERS_ARB,		sDesc.sGLDesc.uMSAASamples > 1 ? GL_TRUE : GL_FALSE,
				WGL_SAMPLES_ARB,			sDesc.sGLDesc.uMSAASamples,
					
				// STEREO //
				WGL_STEREO_ARB,				b_stereo ? GL_TRUE : GL_FALSE,

				// CSAA //
				WGL_COLOR_SAMPLES_NV,		sDesc.sGLDesc.uColorSamples,
				WGL_COVERAGE_SAMPLES_NV,	sDesc.sGLDesc.uMSAASamples,

				0,0
			};

			int rv = 0;

			while ( rv == 0 )
			{
				int		pixelFormat	= 0;
				uint	numFormats	= 0;
				int		valid		= wglChoosePixelFormatARB( _sHandles.hDC, a_attributes, fa_attributes, 1, &pixelFormat, &numFormats );

				if ( valid != 0 and numFormats > 0 )
					rv = pixelFormat;
				else
				{
					if ( a_attributes[25] != 0 or a_attributes[27] != 0 )
					{
						static	bool	_b_switch = true;

						_b_switch = !_b_switch;

						if ( _b_switch and a_attributes[25] != 0 )	// color samples
							a_attributes[25] >>= 1;

						if ( !_b_switch and a_attributes[27] != 0 )	// csaa
							a_attributes[27] >>= 1;
					}
					else
					{
						a_attributes[24] = 0;			// disable csaa
						a_attributes[26] = 0;
							
						if ( a_attributes[23] != 0 )
							a_attributes[23] = 0;		// disable stereo
						else
						if ( a_attributes[21] != 0 )	// msaa
							a_attributes[21] >>= 1;
						else
						if ( a_attributes[13] != 0 )	// stencil buffer
							a_attributes[13] = 0;
						else
						if ( a_attributes[11] > 24 )	// depth buffer
							a_attributes[11] = 24;
						else
						if ( a_attributes[11] != 16 )
							a_attributes[11] = 16;
						else
							break;
					}
				}
			}

			if ( rv != 0 ) {
				u_pixfmt					 = rv;
				_sGLDescriptor.uDepthBits	 = a_attributes[11];
				_sGLDescriptor.uStencilBits	 = a_attributes[13];
				_sGLDescriptor.uMSAASamples	 = a_attributes[27] != 0 ? a_attributes[27] : a_attributes[21];
				_sGLDescriptor.uColorSamples = a_attributes[25];
				
				if ( !DescribePixelFormat( _sHandles.hDC, u_pixfmt, sizeof(pfd), &pfd ) )
					LOG( "Can't describe pixel format", e_log_msg::WARNING_3 );
			}
		}

		_sGLDescriptor.eFlags = e_glinit_flags::type(
									(b_stereo ? e_glinit_flags::STEREO : 0) |
									(b_doublebuf ? e_glinit_flags::DOUBLE_BUFFER : 0) );
		
		CHECK_ERR( u_pixfmt != 0 );
		CHECK_ERR( SetPixelFormat( _sHandles.hDC, u_pixfmt, &pfd ) == TRUE );

		
		// create extended OpenGL rendering context
		if ( sDesc.sGLDesc.eFlags & e_glinit_flags::EXTENDED_CONTEXT and
			 wglCreateContextAttribsARB != null )
		{
			uint		gl_ver[2]	= { (sDesc.sGLDesc.eFlags & e_glinit_flags::_VER_MAJOR_MASK) >> e_glinit_flags::_VER_MAJOR_OFFSET,
										(sDesc.sGLDesc.eFlags & e_glinit_flags::_VER_MINOR_MASK) >> e_glinit_flags::_VER_MINOR_OFFSET };
			int			i_profile	= 0,
						i_flags		= 0;

			// Profile //
			if ( sDesc.sGLDesc.eFlags & e_glinit_flags::CORE_PROFILE )
				i_profile = WGL_CONTEXT_CORE_PROFILE_BIT_ARB;
			else
			if ( sDesc.sGLDesc.eFlags & e_glinit_flags::ES2_PROFILE )
				i_profile = WGL_CONTEXT_ES2_PROFILE_BIT_EXT;
			else
				i_profile = WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
	
			// Flags //
			if ( sDesc.sGLDesc.eFlags & e_glinit_flags::FORWARD_COMPATIBLE )
				i_flags |= WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;

			// Debug //
			if ( sDesc.sGLDesc.eFlags & e_glinit_flags::DEBUG_MODE )
				i_flags |= WGL_CONTEXT_DEBUG_BIT_ARB;


			// create rendering context for OpenGL
			int	a_attribs[] =
			{
				WGL_CONTEXT_MAJOR_VERSION_ARB, gl_ver[0],
				WGL_CONTEXT_MINOR_VERSION_ARB, gl_ver[1],
				WGL_CONTEXT_FLAGS_ARB,         i_flags,
				WGL_CONTEXT_PROFILE_MASK_ARB,  i_profile,
				0
			};

			for (;;)
			{
				_sHandles.hRC = wglCreateContextAttribsARB( _sHandles.hDC, sDesc.sGLDesc.hSharedRC, a_attribs );

				if ( _sHandles.hRC != null or gl_ver[0] == 2 )
					break;
				
				if ( gl_ver[1] == 0 )
				{
					gl_ver[0]--;
					gl_ver[1] = gl_ver[0] > 4 ? 9 : 3;
				}
				else
					gl_ver[1]--;

				a_attribs[1] = gl_ver[0];
				a_attribs[3] = gl_ver[1];

			}

			if ( _sHandles.hRC != null )
			{
				_sGLDescriptor.eFlags |= e_glinit_flags::type(
										((gl_ver[0] << e_glinit_flags::_VER_MAJOR_OFFSET) & e_glinit_flags::_VER_MAJOR_MASK) |
										((gl_ver[1] << e_glinit_flags::_VER_MINOR_OFFSET) & e_glinit_flags::_VER_MINOR_MASK) );
				_sGLDescriptor.eFlags |= sDesc.sGLDesc.eFlags & e_glinit_flags::_CONTEXT_FLAGS_MASK;
			}
		}
		

		// create default OpenGL rendering context
		if ( _sHandles.hRC == null )
		{
			_sHandles.hRC = wglCreateContext( _sHandles.hDC );

			if ( _sHandles.hRC != null and wglMakeCurrent( _sHandles.hDC, _sHandles.hRC ) == TRUE )
			{
				wglShareLists( sDesc.sGLDesc.hSharedRC, _sHandles.hRC );

				GLint	i_major = 0;
				GLint	i_minor = 0;
				glGetIntegerv( GL_MAJOR_VERSION, &i_major );
				glGetIntegerv( GL_MINOR_VERSION, &i_minor );
				
				_sGLDescriptor.eFlags |= e_glinit_flags::type(
										((i_major << e_glinit_flags::_VER_MAJOR_OFFSET) & e_glinit_flags::_VER_MAJOR_MASK) |
										((i_minor << e_glinit_flags::_VER_MINOR_OFFSET) & e_glinit_flags::_VER_MINOR_MASK) );
			}
		}

		
		CHECK_ERR( _sHandles.hRC != null );
		CHECK_ERR( MakeCurrent() );

		SetVSync( sDesc.sGLDesc.bVSync );

		bool	ret = _sContext.Init();
		
		ResetContext();

		if ( !sDesc.bIgnoreGLInitErrors and !ret )
		{
			LOG( "Error while init OpenGL context.", e_log_msg::ERROR_NL_2 );

			const string_array_t &	a_log = _sContext.GetLog();

			FOR( i, a_log ) {
				LOG( a_log[i].cstr(), e_log_msg::ERROR_NL_3 );
			}
			
			_sContext.ClearLog();
		}

		Hide();
		return true;
	}
	
/*
=================================================
	включить или отключить вертикальную синхронизацию
=================================================
*/
	bool GLWindow::SetVSync(bool bEnabled)
	{
		if ( C_WGL_EXT_swap_control )
		{
			int swapint = wglGetSwapIntervalEXT();
			_sGLDescriptor.bVSync = bEnabled;

			if ( _sGLDescriptor.bVSync != (swapint != 0) )
				wglSwapIntervalEXT( _sGLDescriptor.bVSync ? 1 : 0 );

			return true;
		}
		
		_sGLDescriptor.bVSync = false;
		return false;
	}
	
/*
=================================================
	создает окно в отдельном потоке
=================================================
*/
	bool GLWindow::CreateGLInThread (Thread *pThread, const TDescriptorExt &sDesc, uni_c_string pCaption, const TWndClass &sWndClass)
	{
		CHECK_ERR( pThread != null );

		_s_RegisterClass( _sHandles, sWndClass );

		TCreateInThreadDataExt *	p_data = new TCreateInThreadDataExt( this, sDesc, pCaption );
		return pThread->Create( GLWindow::_s_ThreadProcGL, p_data );
	}
	
/*
=================================================
	для запуска в отдельном потоке
=================================================
*/
	void GLWindow::_s_ThreadProcGL (void *pParam)
	{
		TCreateInThreadDataExt *	p_data = (TCreateInThreadDataExt *)pParam;
		GLWindow				*	p_wnd  = p_data->pThis;

		p_wnd->_CreateGL( p_data->sDesc, p_data->sCaption.cstr() );
		delete p_data;

		p_wnd->Loop();
	}
		
/*
=================================================
	удалить окно
=================================================
*/
	void GLWindow::Destroy ()
	{
		if ( _sHandles.hDC != null )
		{
			wglMakeCurrent( _sHandles.hDC, null );

			if ( _sHandles.hRC != null )
			{
				_sContext.Clear();
				wglDeleteContext( _sHandles.hRC );
				_sHandles.hRC = null;
			}

			ReleaseDC( _sHandles.hWnd, _sHandles.hDC);
			_sHandles.hDC = null;
		}

		if ( _sDescriptor.bFullScreen )
			ChangeDisplaySettings( (DEVMODEA *)null, CDS_RESET );
		
		_sGLDescriptor = TGLDescriptor();

		CWindow::Destroy();
	}

/*
=================================================
	поток обработки сообщений
=================================================
*/
	int GLWindow::Loop()
	{
		MSG		s_msg = {0};
		_bIsLooping = true;
		
		SetWindowLongPtrA( _sHandles.hWnd, GWLP_USERDATA, (LONG_PTR) this );
		SetWindowLongPtrA( _sHandles.hWnd, GWLP_WNDPROC,  (LONG_PTR) &_s_WinProc );
		Show();
		
		MakeCurrent();

		while ( _bIsLooping )
		{
			while ( PeekMessageA( &s_msg, null, 0, 0, PM_REMOVE ) )
			{
				if ( WM_QUIT == s_msg.message )
					_bIsLooping = false;
				else
				{   
					DispatchMessageA(  &s_msg );
				}
			}

			_Update();
		}
		
		ResetContext();
		SetWindowLongPtrA( _sHandles.hWnd, GWLP_WNDPROC, (LONG_PTR)DefWindowProcA );

		CHECK_ERR( ((int)s_msg.wParam) != -1 );
		return true;
	}

//-------------------------------------------------------------------

}	// _platform_
}	// UX_STL

#endif	// UX_OPENGL_WINDOW_HANDLES
#endif	// PLATFORM_WINDOWS_SHELL